#include "codeediting.h"
#include "ui_codeediting.h"

#include <QFileDialog>

/*
在Qt里，QFile::map提供了跨平台的“文件内存映射”支持。所以通过调用QFile::map就可以把文件“加载”为一个const char*字符串使用。
我们知道，在 QPlainTextEdit里，显示文本一般可以用setPlainText。如果直接把map后的内存传递给setPlainText会导致文件的所有内容被读入内存，这显然是不行的。
一般对大文件处理方式是“分页”，也就是一次只加载部分内容。
为了让用户感知不到文件被“分页”了，我们需要处理下，自动加载分页的内容。具体的做法：
监听滚动事件，自动加载下一个/上一个分页
隐藏滚动条，用外部滚动条替代；外部滚动条对应整个文件范围，并保持实时同步
*/

CodeEditing::CodeEditing(const QString &name, QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::CodeEditing)
    , fileName(name)
{
    ui->setupUi(this);
    ui->linenum->setAttribute(Qt::WA_TransparentForMouseEvents, true);

    QScrollBar *verticalScrollBar = ui->redact->verticalScrollBar();
    // 连接滚动条的信号到槽函数sliderMoved
    connect(verticalScrollBar, &QScrollBar::valueChanged, this, [this](int position){
        ui->linenum->verticalScrollBar()->setValue(position);
    });
}

CodeEditing::~CodeEditing()
{
    delete ui;
    highlighter = nullptr;
}

void CodeEditing::loadText(const QString& filePath)
{
    toLoadIF = false;
    this->filePath = filePath;
    QFile *file = new QFile(filePath);
    if (!file->open(QIODevice::ReadOnly | QIODevice::Text)){
        // 文件无法打开，处理错误
        qDebug() << "Failed to open file:" << filePath;
        return ;
    }
    // 创建一个QString流读取文件内容
    QTextStream *in = new QTextStream(file);
    QString text = in->read(20480);
    ui->redact->setPlainText(text);

    //如果读取完就不开启线程了
    if (in->atEnd()) {
        file->close();
        delete in;
        delete file;
        toLoadIF = true;
        connect(ui->redact, &QPlainTextEdit::textChanged, this, &CodeEditing::textChanged);
        return;
    }

    fileLoaderThread = new QThread(this);
    fileLoader = new FileLoader(in);
    fileLoader->moveToThread(fileLoaderThread);
    connect(fileLoaderThread, &QThread::started, fileLoader, &FileLoader::loadFile);
    connect(fileLoader, &FileLoader::chunkLoaded, this, [this](const QString &chunk){
        //确保在主线程中更新界面
        QMetaObject::invokeMethod(this, [this, chunk]{
                ui->redact->appendPlainText(chunk);
            }, Qt::QueuedConnection);
    });
    connect(fileLoader, &FileLoader::finished, this, [file, in, this]{
        QMetaObject::invokeMethod(this, [this]{
                disconnect(fileLoader, nullptr, this, nullptr);
                disconnect(fileLoaderThread, nullptr, this, nullptr);
            }, Qt::QueuedConnection);
        qDebug() << "关闭";

        fileLoaderThread->quit();
        fileLoaderThread->wait();
        fileLoaderThread->deleteLater();
        fileLoader->deleteLater();
        file->close();
        delete in;
        delete file;
        toLoadIF = true;
        connect(ui->redact, &QPlainTextEdit::textChanged, this, &CodeEditing::textChanged);
    });

    fileLoaderThread->start();
    return ;
}

void CodeEditing::linguisticType(const QString &type)
{
    if (type == "cpp"){
        highlighter = std::make_unique<CodeHighlighter>(ui->redact->document(), Language::Cpp);  // 应用高亮
    }
}

void CodeEditing::saveFile()
{
    if (filePath.isEmpty()){
        filePath =  QFileDialog::getExistingDirectory(this,tr("Select Directory"), QDir::homePath());
        filePath += "/" + fileName;
        qDebug() << filePath;
    }
    QString text = ui->redact->toPlainText();
    QFile file(filePath);

    // 打开文件以写入
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream stream(&file);
        stream << text;
        file.close(); // 关闭文件
        saveIF = true;
        emit amend(saveIF);
    } else {
        qDebug() << "无法打开文件以写入";
    }
}

void CodeEditing::ChangeHighlighting(const Language language)
{
    qDebug("切换");
    highlighter = std::make_unique<CodeHighlighter>(ui->redact->document(), language);  // 应用高亮
}

void CodeEditing::lineNumber(const int lastTime, int change)
{
    if (change >= 0){
        for (int i = 1; i <= change; i++){
            ui->linenum->appendPlainText(QString::number(lastTime + i));
        }
        return;
    }

    QTextDocument *doc = ui->linenum->document();
    QTextBlock block = doc->lastBlock();
    while(change++ && block.isValid()){
        QTextCursor cursor(block);
        cursor.select(QTextCursor::BlockUnderCursor);  // 选择最后一段
        cursor.removeSelectedText();  // 删除选中的段落
        block = block.previous();
    }
}

void CodeEditing::on_redact_blockCountChanged(int newBlockCount)
{
    lineNumber(lastTime, newBlockCount - lastTime);
    lastTime = newBlockCount;
}

void CodeEditing::textChanged()
{
    if (!ui->redact->document()->isModified() || !saveIF){
        return;
    }
    saveIF = false;
    emit amend(saveIF);
}

void CodeEditing::on_redact_cursorPositionChanged()
{
    // 获取光标位置
    QTextCursor cursor = ui->redact->textCursor();
    // 高亮单词和背景
    highlightWordWithBackground(cursor);
}
void CodeEditing::highlightWordWithBackground(QTextCursor cursor)
{
    // 为整行设置浅色背景
    QTextEdit::ExtraSelection selection;
    selection.format.setBackground(QColor(234,234,242));
    selection.format.setProperty(QTextFormat::FullWidthSelection, true);
    selection.cursor = ui->redact->textCursor();
    selection.cursor.clearSelection();

    // 获取当前光标位置的单词
    cursor.select(QTextCursor::WordUnderCursor);

    // 为单词设置高亮颜色
    QTextEdit::ExtraSelection wordSelection;
    wordSelection.cursor = cursor;
    wordSelection.format.setBackground(Qt::gray);  // 设置单词背景为深色，突显效果

    // 添加高亮效果
    QList<QTextEdit::ExtraSelection> extraSelections;
    extraSelections.append(selection);  // 整行高亮
    extraSelections.append(wordSelection);  // 单词高亮

    ui->redact->setExtraSelections(extraSelections);
}
